//+---------------------------------------------------------------------------
//
// Copyright (C) Microsoft Corporation, 2008
//
// File: DataPointsLogger.cs
//
// Contents: The data points logger singleton class
//
// Created by: Adam Wardynski (AdamWa) 2/4/2008
//
//----------------------------------------------------------------------------
using System;
using System.Collections;
using System.Collections.Specialized;
using System.IO;
using System.Configuration;
using System.Diagnostics;
using System.Text;
using System.Timers;
using System.IO.Compression;
using System.Xml;
using System.Security.Permissions;

[assembly: SecurityPermissionAttribute(SecurityAction.RequestMinimum, Assertion = true)]
namespace Microsoft.Reputation.Services
{
    /// <summary>
    /// This class implements the singleton design pattern and it has all limitation associated with a singleton
    /// </summary>
    public class DataPointsLogger
    {
        private Queue m_queue;
        private Timer m_timer;
        private string m_logFileName = String.Empty;
        private Stream m_logFileStreamWriter;
        private int m_numLogsInFile;
        private DataPointSchema m_schema;
        private int m_maxNumLogsPerFile; // make read only
        private string m_dropLocation; // make read only
        private int m_maxSizeOfLogFile; // make read only
        private int m_recordsBetweenLogFileRefresh; // make read only
        private string m_failedLocation;
        private int m_maxRetries;
        private int m_failedInterval;
        private int m_interval;
        private string m_prefix;
        private string m_tempLocation;

        private bool m_useCompression;
        private bool m_active;
        private string m_header;
        private Queue m_failedQueue;
        private Timer m_failedTimer;
        private static System.Diagnostics.Stopwatch m_stopWatch = new System.Diagnostics.Stopwatch();

        private static volatile DataPointsLogger s_logger = new DataPointsLogger();

        private PerformanceCounter m_totalLogFilesGenerated;
        private PerformanceCounter m_totalLogFilesGeneratedPerSec;
        private PerformanceCounter m_activeQueueSize;
        private PerformanceCounter m_processingQueueSize;
        private PerformanceCounter m_failedFileQueueSize;
        private PerformanceCounter m_totalDataPointsEnqueued;
        private PerformanceCounter m_totalDataPointsEnqueuedPerSec;
        private PerformanceCounter m_totalDataPersistThroughput;
        private bool m_perfCountersEnabled;

        private const string PerformanceCountersCategory = "Reputation Services";
        private static string s_performanceCounterPrefix;

        private static object s_synchronization = new object();

        /// <summary>
        /// Private Ctor
        /// </summary>
        private DataPointsLogger()
        {
            TraceUtility.TraceHeader(DiagnosticTracing.Source);
            using (TraceUtility.TraceMethod(DiagnosticTracing.Source))
            {
                NameValueCollection settings =
                    (NameValueCollection)ConfigurationManager.GetSection("DataPointsLogger/Settings");

                // all setting are required
                m_maxNumLogsPerFile = Int32.Parse(settings["MaxLogsPerFile"]);
                if (m_maxNumLogsPerFile < 1)
                {
                    throw new ConfigurationErrorsException("MaxLogsPerFile must be greater than 0");
                }

                m_maxSizeOfLogFile = Int32.Parse(settings["MaxSizeOfLogFile"]);
                if (m_maxSizeOfLogFile < 1)
                {
                    throw new ConfigurationErrorsException("MaxSizeOfLogFile must be greater than 0");
                }

                m_recordsBetweenLogFileRefresh = Int32.Parse(settings["RecordsBetweenLogFileRefresh"]);
                if (m_recordsBetweenLogFileRefresh < 1)
                {
                    throw new ConfigurationErrorsException("RecordsBetweenLogFileRefresh must be greater than 0");
                }

                m_interval = Int32.Parse(settings["ProcessQueueInterval"]);
                if (m_interval < 0)
                {
                    throw new ConfigurationErrorsException("ProcessQueueInterval must be greater than 0 (or equal)");
                }

                m_maxRetries = Int32.Parse(settings["FailedRetryMaxAttempts"]);
                if (m_maxRetries < 0)
                {
                    throw new ConfigurationErrorsException("FailedRetryMaxAttempts must be greater than 0");
                }

                m_failedLocation = settings["FailedLocation"];
                m_failedInterval = Int32.Parse(settings["FailedRetryInterval"]);
                m_dropLocation = settings["DropLocation"];
                m_tempLocation = settings["TempLocation"];
                m_useCompression = Boolean.Parse(settings["UseCompression"]);
                bool addSchemaToHeader = Boolean.Parse(settings["AddSchemaToHeader"]);
                m_prefix = settings["LogFilePrefix"];

                m_perfCountersEnabled = false; // you have to InitializePerformanceCounters() to enable them

                m_schema = (DataPointSchema)ConfigurationManager.GetSection("DataPointsLogger/dataPointSchema");

                // set the header
                if (addSchemaToHeader)
                {
                    this.Header = m_schema.Xml;
                }
            }
        }

        /// <summary>
        /// Sets the logger to begin logging
        /// if already active nothing happens
        /// </summary>
        public void StartLogging()
        {
            using (TraceUtility.TraceMethod(DiagnosticTracing.Source))
            {
                if (m_active)
                {
                    return;
                }

                // create sync queue
                m_queue = Queue.Synchronized(new Queue(100));

                // create the initial log file
                GetNewLogFile(true);

                // created failed queue
                m_failedQueue = Queue.Synchronized(new Queue());

                // setup and start Failed Timer
                m_failedTimer = new System.Timers.Timer(1000 * m_failedInterval);

                // setup and start Timer
                // every interval the in memory queue is writen to the log file
                m_timer = new System.Timers.Timer(1000 * m_interval);

                m_failedTimer.Elapsed += new System.Timers.ElapsedEventHandler(ProcessFailedQueue);
                m_timer.Elapsed += new System.Timers.ElapsedEventHandler(ProcessQueue);

                // enable the timers
                m_timer.Enabled = true;
                m_failedTimer.Enabled = true;
                // set the active flag
                m_active = true;
            }
        }

        /// <summary>
        /// Accessor for singleton.  Only way to get a logger instance.
        /// </summary>
        public static DataPointsLogger GetInstance
        {
            get
            {
                return s_logger;
            }
        }

        /// <summary>
        /// Get the maximum number of log entries that a log file can have
        /// before it opens a new log.
        /// </summary>
        public int MaxEntriesPerLogFile
        {
            get 
            { 
                return m_maxNumLogsPerFile; 
            }
        }

        /// <summary>
        /// Initialize event log
        /// </summary>
        public void Initialize(
            EventLog eventLog,
            int eventIdBase,
            int antiEventIdBase,
            bool eventLoggerThrottleDisabled,
            string performanceCounterPrefix)
        {
            using (TraceUtility.TraceMethod(DiagnosticTracing.Source))
            {
                DataPointsEventLogger.EventLog = eventLog;
                DataPointsEventLogger.EventIdBase = eventIdBase;
                DataPointsEventLogger.AntiEventIdBase = antiEventIdBase;
                DataPointsEventLogger.EventLoggerThrottleDisabled = eventLoggerThrottleDisabled;
                DataPointsEventLogger.EventLoggerActive = eventLog != null;
                s_performanceCounterPrefix = performanceCounterPrefix;
                InitializePerformanceCounters();
            }
        }

        /// <summary>
        /// Get only Accessor for checking if PerfCounters are enabled
        /// </summary>
        public bool PerfCountersEnabled
        {
            get
            {
                return m_perfCountersEnabled;
            }
        }

        /// <summary>
        /// Set only Accessor for the header
        /// This header will be added to the begining of every log file
        /// This overrides the AddSchemaToHeader property
        /// </summary>
        public string Header
        {
            set
            {
                if (value == null)
                {
                    throw new ArgumentNullException("value");
                }

               // check to ensure new header does not contain \n (it is used as a delimiter in the Log Service
                if (value.IndexOf('\n') != -1)
                {
                    throw new ArgumentException("Header cannot contain \"\\n\" (newline)", "Header");
                }

                // add trailing newline as delimiter
                m_header = value + '\n';
            }
        }

        /// <summary>
        /// Adds data to the log queue to be written to the log file
        /// </summary>
        /// <param name="data">The DataPoint with data to be logged</param>
        public void Enqueue(DataPoint data)
        {
            using (TraceUtility.TraceMethod(DiagnosticTracing.Source))
            {
                // make sure logging is still possible
                if (!m_active)
                {
                    throw new LoggerInActiveException();
                }

                // validate data
                if (!m_schema.IsValid(data))
                {
                    throw new DataPointSchemaValidationException();
                }

                //
                // There is a possibility that the Enqueue thread gets hold of the m_queue before
                // GetQueue thread creates a new m_queue. So, there is a race condition that the 
                // Enqueue thread queues the data to the old m_queue after the old m_queue has 
                // been flushed to the disk. To avoid this race condition, we need to synchronize
                // Enqueue thread with the GetQueue thread.
                //
                lock (s_synchronization)
                {
                    // thread safe add
                    m_queue.Enqueue(data);
                }

                // update perf counters
                if (m_perfCountersEnabled)
                {
                    m_totalDataPointsEnqueued.Increment();
                    m_totalDataPointsEnqueuedPerSec.Increment();
                    m_activeQueueSize.Increment();
                }
            }
        }

        /// <summary>
        /// Flushes remaining in memory to the log file and drop the log file.  
        /// After this method is called no more calls to Enqueue can take place.
        /// </summary>
        public void FlushLogFileAndStopLogging()
        {
            using (TraceUtility.TraceMethod(DiagnosticTracing.Source))
            {
                // check to make sure its not already closed
                if (!m_active)
                {
                    throw new LoggerInActiveException();
                }

                // kill queueing timer
                m_timer.Dispose();
                m_timer = null;

                // set closed variable
                m_active = false;

                // process queue one last time
                ProcessQueue(true);

                // kill failed timer
                m_failedTimer.Dispose();
                m_failedTimer = null;

                // process failed queue one last time
                ProcessFailedQueue(null, null);
            }
        }

        /// <summary>
        /// Swaps out the "active" queue, and creates a new one, the old "active" queue is returned for processing
        /// </summary>
        /// <returns>the old "active" queue aka the "processing" queue</returns>
        private Queue GetQueue()
        {
            using (TraceUtility.TraceMethod(DiagnosticTracing.Source))
            {
                Queue tmp;

                //
                // There is a possibility that the Enqueue thread gets hold of the m_queue before
                // GetQueue thread creates a new m_queue. So, there is a race condition that the 
                // Enqueue thread enqueues the data to the old m_queue after the old m_queue has 
                // been flushed to the disk. To avoid this race condition, we need to synchronize
                // Enqueue thread with the GetQueue thread.
                //
                lock (s_synchronization)
                {
                    // swap out the current queue
                    tmp = m_queue;

                    m_queue = Queue.Synchronized(new Queue(100));
                }

                if (m_perfCountersEnabled)
                {
                    m_processingQueueSize.RawValue = tmp.Count;
                    m_activeQueueSize.RawValue = 0;
                }

                return tmp;
            }
        }

        /// <summary>
        /// Method which processes the items on the Failed Queue.
        /// These items represent log files that are in the Failed Location but could not be placed in the Drop Location.
        /// Called by the Failed Timer
        /// </summary>
        /// <param name="obj">NA</param>
        private void ProcessFailedQueue(Object source, System.Timers.ElapsedEventArgs args)
        {
            using (TraceUtility.TraceMethod(DiagnosticTracing.Source))
            {
                if (m_failedTimer != null)
                {
                    m_failedTimer.Enabled = false;
                }

                object o;
                FailedFileInfo fileInfo;
                while (m_failedQueue.Count > 0)
                {
                    // read item at top of queue
                    o = m_failedQueue.Peek();
                    fileInfo = (FailedFileInfo)o;

                    // check to see if its time to retry
                    if (fileInfo.NextRetryTime <= DateTime.Now)
                    {
                        // yes, sufficient time has passed
                        // pop item off queue
                        o = m_failedQueue.Dequeue();
                        fileInfo = (FailedFileInfo)o;

                        if (m_perfCountersEnabled)
                        {
                            m_failedFileQueueSize.Decrement();
                        }

                        // get destination path
                        string name = Path.GetFileName(fileInfo.FileName);
                        string destination = Path.Combine(m_dropLocation, name);

                        // attempt move
                        try
                        {
                            File.Move(fileInfo.FileName, destination);
                            DataPointsEventLogger.LogProcessFailedQueueMoveFileSuccess();
                            DataPointsEventLogger.LogProcessFailedQueueMoveFileMaxSuccess();
                        }
                        catch (Exception ex)
                        {
                            TraceUtility.TraceCatchException(DiagnosticTracing.Source, ex);

                            // log failure
                            DataPointsEventLogger.LogProcessFailedQueueMoveFileFailure(
                                "Unable to move [" + fileInfo.FileName + "] to drop location [" + destination + "] " + ex);

                            // add failure to tracking object
                            fileInfo.AddFailure(m_failedInterval);

                            // check if max failures reached
                            if (fileInfo.NumTries >= m_maxRetries)
                            {
                                // yes, max failures reached
                                DataPointsEventLogger.LogProcessFailedQueueMoveFileMaxFailure(
                                    "Max attempts reached moving [" + fileInfo.FileName + "] to drop location [" + destination + "] " + ex);
                            }
                            else
                            {
                                // no, max failures not yet reached
                                // add to failed queue to retry later
                                m_failedQueue.Enqueue(fileInfo);

                                if (m_perfCountersEnabled)
                                {
                                    m_failedFileQueueSize.Increment();
                                }

                            }
                        }
                    }
                    else
                    {
                        break; // next item is not yet ready to be retried, therefore, no other item on queue is either
                    }
                }

                if (m_failedTimer != null)
                {
                    m_failedTimer.Enabled = true;
                }
            }
        }

        /// <summary>
        /// Method which processes the "active" queue and writes the data to the log file.
        /// Called by the timer
        /// </summary>
        /// <param name="source">object</param>
        /// <param name="args">System.Timers.ElapsedEventArgs</param>
        private void ProcessQueue(Object source, System.Timers.ElapsedEventArgs args)
        {
            ProcessQueue(false);
        }

        /// <summary>
        /// Method which processes the "active" queue and writes the data to the log file.
        /// Called by the timer
        /// </summary>
        /// <param name="obj">flush bool</param>
        private void ProcessQueue(bool flush)
        {
            using (TraceUtility.TraceMethod(DiagnosticTracing.Source))
            {
                int persistedItems = 0;
                long persistedDataSize = 0;

                try
                {
                    m_stopWatch.Start();

                    if (m_timer != null)
                    {
                        m_timer.Enabled = false;
                    }

                    // swap out current "active" queue
                    Queue currentQueue = GetQueue();

                    FileInfo fileInfo = null;
                    bool getFileInfo = true;
                    Object o;
                    DataPoint data;
                    string tmpData;
                    byte[] buffer;

                    // iterate through all data on the "process" queue
                    while (currentQueue.Count > 0)
                    {
                        // first get log file info but only when needed
                        try
                        {
                            if (getFileInfo)
                            {
                                fileInfo = new FileInfo(m_logFileName);

                                // dont need to get FileInfo again because we just got it
                                getFileInfo = false;

                                DataPointsEventLogger.LogProcessQueueGetFileInfoSuccess();
                            }
                        }
                        catch (Exception ex)
                        {
                            TraceUtility.TraceCatchException(DiagnosticTracing.Source, ex);

                            DataPointsEventLogger.LogProcessQueueGetFileInfoFailure(
                                "Failed to get log file info [" + m_logFileName + "] " + ex);

                            return;
                        }

                        // get next data point
                        o = currentQueue.Dequeue();
                        data = (DataPoint)o;

                        //				if( data == null )
                        //				{
                        //					LogMessage( "Object off queue is not DataPoint.", EventLogEntryType.Warning, 5001 );
                        //					continue; // cant happen, but oh well skip this data
                        //				}

                        if (m_perfCountersEnabled)
                        {
                            m_processingQueueSize.Decrement();
                        }

                        // write data to tmp log file
                        try
                        {
                            // get data as a string
                            tmpData = m_schema.WriteData(data);

                            // convert to byte array
                            buffer = Encoding.UTF8.GetBytes(tmpData);

                            // write to stream
                            m_logFileStreamWriter.Write(buffer, 0, buffer.Length);

                            persistedDataSize += buffer.Length;
                            persistedItems++;

                            if (m_perfCountersEnabled)
                            {
                                m_totalDataPersistThroughput.IncrementBy(buffer.Length);
                            }

                            DataPointsEventLogger.LogProcessQueueStreamWriteSuccess();
                        }
                        catch (Exception ex)
                        {
                            TraceUtility.TraceCatchException(DiagnosticTracing.Source, ex);
                            DataPointsEventLogger.LogProcessQueueStreamWriteFailure("Error writing to temp file [" + m_logFileName + "] " + ex);

                            continue; // skip this data point ... oh well
                        }
                        
                        // update number of records in file
                        m_numLogsInFile++;

                        // refresh file info object as needed so fileInfo.Length is accurate
                        try
                        {
                            if (currentQueue.Count % m_recordsBetweenLogFileRefresh == 0)
                            {
                                // Flush the contents of the logFileStreamWriter to disk. 
                                // This aids in proper estimation of file size when fileInfo.Refresh() is invoked below						
                                m_logFileStreamWriter.Flush();
                                fileInfo.Refresh();

                                DataPointsEventLogger.LogProcessQueueStreamFlushSuccess();
                            }
                        }
                        catch (Exception ex)
                        {
                            TraceUtility.TraceCatchException(DiagnosticTracing.Source, ex);
                            DataPointsEventLogger.LogProcessQueueStreamFlushFailure("Error refreshing temp file info [" + m_logFileName + "] " + ex);

                            // can continue this loop (might get other exceptions though)
                        }

                        // check to see if a new log file is needed
                        try
                        {
                            // check both number of record and size of log file
                            if ((!m_useCompression && (m_numLogsInFile >= m_maxNumLogsPerFile ||
                                fileInfo.Length >= m_maxSizeOfLogFile)) ||
                                (m_useCompression && m_numLogsInFile >= m_maxNumLogsPerFile))
                            {
                                // yep, a new log file is need so create one
                                GetNewLogFile(true);
                                // make sure to update the FileInfo variable on next iteration
                                getFileInfo = true;

                                DataPointsEventLogger.LogProcessQueueCreateNewFileSuccess();
                            }
                        }
                        catch (Exception ex)
                        {
                            TraceUtility.TraceCatchException(DiagnosticTracing.Source, ex);
                            DataPointsEventLogger.LogProcessQueueCreateNewFileFailure("Error getting new log file [" + m_logFileName + "] " + ex);
                            break; // stop processing all togeather -- major problem
                        }
                    }

                    // if this is a flush, close the currently opened log file
                    try
                    {
                        if (flush)
                        {
                            GetNewLogFile(false);
                            getFileInfo = true;
                            DataPointsEventLogger.LogProcessQueueGetNewFileSuccess();
                        }
                    }
                    catch (Exception ex)
                    {
                        TraceUtility.TraceCatchException(DiagnosticTracing.Source, ex);
                        DataPointsEventLogger.LogProcessQueueGetNewFileFailure("Error flushing log file [" + m_logFileName + "] " + ex);
                    }

                    if (m_timer != null)
                    {
                        m_timer.Enabled = true;
                    }
                }
                finally
                {
                    m_stopWatch.Stop();

                    TraceUtility.TraceInfo(
                        DiagnosticTracing.Source,
                        "[PERF] ProcessQueue persisted {0} bytes in {1} items within [{2} ms]",
                        persistedDataSize,
                        persistedItems,
                        m_stopWatch.ElapsedMilliseconds);
                }
            }
        }

        /// <summary>
        /// closes current tmp log file and attempts to move it to drop share (otherwise fail locaiton)
        /// </summary>
        /// <param name="createNew">if true a new log file will be create, if false the existing log file will be closed</param>
        private void GetNewLogFile(bool createNew)
        {
            using (TraceUtility.TraceMethod(DiagnosticTracing.Source))
            {
                // exceptions handled by caller

                string oldLogFileName = m_logFileName;

                // get reference to existing stream
                Stream oldLogFileStreamWriter = m_logFileStreamWriter;

                // create file stream with write to new temp file
                FileStream innerStream = null;
                if (createNew)
                {
                    // get new tmp file name
                    // use tempLocation
                    m_logFileName = Path.Combine(m_tempLocation, Guid.NewGuid().ToString() + ".tmp");

                    // this will create the file
                    innerStream = new FileStream(m_logFileName, FileMode.Append, FileAccess.Write);

                    // set the stream for writing
                    if (m_useCompression)
                    {
                        m_logFileStreamWriter = new DeflateStream(innerStream, CompressionMode.Compress);
                    }
                    else
                    {
                        m_logFileStreamWriter = innerStream;
                    }
                }

                // close old file (if it exists)
                if (oldLogFileStreamWriter != null)
                {
                    // exceptions handled by caller
                    oldLogFileStreamWriter.Flush();
                    oldLogFileStreamWriter.Close();
                }

                // move old file (if it exists) and there are records in it
                if (!string.IsNullOrEmpty(oldLogFileName) && m_numLogsInFile > 0)
                {
                    if (m_perfCountersEnabled)
                    {
                        m_totalLogFilesGenerated.Increment();
                        m_totalLogFilesGeneratedPerSec.Increment();
                    }

                    // destination path with new file name
                    string newFileName = String.Concat(m_prefix, Guid.NewGuid(), "_", Environment.MachineName, ".log");
                    string newPath = Path.Combine(m_dropLocation, newFileName);

                    // attempt to move existing temp file to drop locaiton with new name
                    try
                    {
                        File.Move(oldLogFileName, newPath);
                        DataPointsEventLogger.LogGetNewLogFileMoveSuccess();
                    }
                    catch (Exception ex)
                    {
                        TraceUtility.TraceCatchException(DiagnosticTracing.Source, ex);

                        // move to drop location failure
                        DataPointsEventLogger.LogGetNewLogFileMoveFailure(
                            "Unable to move [" + oldLogFileName + "] to drop location [" + newPath + "] " + ex);

                        // attempt move to fail location
                        string failPath = Path.Combine(m_failedLocation, newFileName);
                        try
                        {
                            File.Move(oldLogFileName, failPath);

                            // add to failed queue
                            FailedFileInfo info = new FailedFileInfo(failPath);
                            // add current failure
                            info.AddFailure(m_failedInterval);
                            // put file on failed queue
                            m_failedQueue.Enqueue(info);

                            if (m_perfCountersEnabled)
                            {
                                m_failedFileQueueSize.Increment();
                            }

                            DataPointsEventLogger.LogGetNewLogFileMoveToFailPathSuccess();
                        }
                        catch (Exception ex2)
                        {
                            TraceUtility.TraceCatchException(DiagnosticTracing.Source, ex2);

                            // failed to move to failed location
                            // file is lost :(
                            DataPointsEventLogger.LogGetNewLogFileMoveToFailPathFailure(
                                "Failed to move [" + oldLogFileName + "] " + "to failover location [" + failPath + "] " + ex2);

                            // attemp to delete the temp log file
                            try
                            {
                                File.Delete(oldLogFileName);
                                DataPointsEventLogger.LogGetNewLogFileDelTempSuccess();
                            }
                            catch (Exception ex3)
                            {
                                TraceUtility.TraceCatchException(DiagnosticTracing.Source, ex3);

                                // cant clean temp file, record file name so it can be manually cleaned
                                DataPointsEventLogger.LogGetNewLogFileDelTempFailure(
                                    "Failed to delete temp log file [" + oldLogFileName + "] " + ex3);
                            }
                        }
                    }
                }
                else
                {
                    if (!string.IsNullOrEmpty(oldLogFileName) && m_numLogsInFile == 0)
                    {
                        // when flush is called if current (or previous) log file is empty we want to delete it from the temp drive 
                        // or whenever there is an empty file delete the temp file
                        try
                        {
                            File.Delete(oldLogFileName);
                            DataPointsEventLogger.LogGetNewLogFileDelTempSuccess();
                        }
                        catch (Exception ex4)
                        {
                            TraceUtility.TraceCatchException(DiagnosticTracing.Source, ex4);

                            // cant clean temp file, record file name so it can be manually cleaned
                            DataPointsEventLogger.LogGetNewLogFileDelTempFailure(
                                "Failed to delete temp log file [" + oldLogFileName + "] " + ex4);
                        }
                    }
                }
                // write header to new file (only if there is a header)
                // get the header as a byte array first
                if (createNew && m_header != null && m_header.Length > 0)
                {
                    byte[] buffer = Encoding.UTF8.GetBytes(m_header);
                    m_logFileStreamWriter.Write(buffer, 0, buffer.Length);
                }

                // resest size of log file
                m_numLogsInFile = 0;
            }
        }

        /// <summary>
        /// Initializes the performance counters
        /// </summary>
        public void InitializePerformanceCounters()
        {
            if (!PerformanceCounterCategory.Exists(PerformanceCountersCategory))
            {
                DataPointsEventLogger.LogPerformanceCountersInitializationFailure(
                    "Performance Counters Category does not exist.");

                return;
            }

            try
            {
                m_totalLogFilesGenerated = new PerformanceCounter(
                    PerformanceCountersCategory,
                    s_performanceCounterPrefix + "DataFilesGeneratedTotal", 
                    false);

                m_totalLogFilesGeneratedPerSec = new PerformanceCounter(
                    PerformanceCountersCategory,
                    s_performanceCounterPrefix + "DataFilesGeneratedPerSec", 
                    false);

                m_totalDataPointsEnqueued = new PerformanceCounter(
                    PerformanceCountersCategory,
                    s_performanceCounterPrefix + "DataPointsEnqueuedTotal", 
                    false);

                m_totalDataPointsEnqueuedPerSec = new PerformanceCounter(
                    PerformanceCountersCategory,
                    s_performanceCounterPrefix + "DataPointsEnqueuedPerSec", 
                    false);

                m_activeQueueSize = new PerformanceCounter(
                    PerformanceCountersCategory,
                    s_performanceCounterPrefix + "ActiveQueueSize", 
                    false);

                m_processingQueueSize = new PerformanceCounter(
                    PerformanceCountersCategory,
                    s_performanceCounterPrefix + "ProcessingQueueSize", 
                    false);

                m_failedFileQueueSize = new PerformanceCounter(
                    PerformanceCountersCategory,
                    s_performanceCounterPrefix + "FailedFileQueueSize", false);

                m_totalDataPersistThroughput = new PerformanceCounter(
                    PerformanceCountersCategory,
                    s_performanceCounterPrefix + "DataBytesPersistedPerSec",
                    false);

                // After we have created the counters in memory they do not truly
                // exist yet as callable entities.  We want to "touch" each counter
                // to make sure it exists and throw the Event here before we go any
                // further in the initialization process.
                m_totalLogFilesGenerated.NextSample();
                m_totalLogFilesGeneratedPerSec.NextSample();
                m_totalDataPointsEnqueued.NextSample();
                m_totalDataPointsEnqueuedPerSec.NextSample();
                m_activeQueueSize.NextSample();
                m_processingQueueSize.NextSample();
                m_failedFileQueueSize.NextSample();
                m_totalDataPersistThroughput.NextSample();

                DataPointsEventLogger.LogPerformanceCountersInitializationSuccess();
                m_perfCountersEnabled = true;
            }
            catch (Exception ex)
            {
                TraceUtility.TraceCatchException(DiagnosticTracing.Source, ex);

                DataPointsEventLogger.LogPerformanceCountersInitializationFailure(
                    "Failed to initialize Perf Counters for Common Logger.  " + ex);
            }
        }
    }
}